/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.SPI.config;

import com.aduib.boot.common.SPI.SPIDefinition;
import com.aduib.boot.common.SPI.metadata.ConstructorMetaData;
import com.aduib.boot.common.SPI.metadata.MetaDataBuilder;
import com.aduib.boot.common.concurrent.JctoolsHelper;
import com.aduib.boot.common.exception.CommonException;
import com.aduib.boot.common.lang.SimpleCache;
import com.aduib.boot.common.proxy.Proxy;
import com.aduib.boot.common.proxy.ProxyCreator;
import com.aduib.boot.common.proxy.ProxyFactory;
import com.aduib.boot.common.proxy.factory.ByteBuddyProxyFactory;
import com.aduib.boot.common.proxy.factory.CglibProxyFactory;
import com.aduib.boot.common.proxy.factory.JdkProxyFactory;
import com.aduib.boot.common.proxy.support.ByteBuddyProxy;
import com.aduib.boot.common.proxy.support.CglibProxy;
import com.aduib.boot.common.proxy.support.JdkProxy;
import com.aduib.boot.common.util.MapUtils;
import com.aduib.boot.common.util.ObjectUtils;

import java.util.Set;

/**
 * @description: ProxySpiPostProcessor
 * @author: zzh
 * @date: 2021/10/29 16:03
 */
public class ProxySpiAutoCreator implements SpiPostProcessor {

  private final SimpleCache<Class<?>, Proxy> PROXY_CACHE = new SimpleCache<>(JctoolsHelper.nonBlockingHashMap());

  private final SimpleCache<Class<?>, Object> PROXY_INSTANCE_CACHE = new SimpleCache<>(JctoolsHelper.nonBlockingHashMap());

  private final Set<Class<?>> EXCLUDE_PROXY_CACHE = MapUtils.newHashSet();

  public ProxySpiAutoCreator() {
    initExcludeProxyClass();
  }

  private void initExcludeProxyClass() {
    EXCLUDE_PROXY_CACHE.add(ProxyFactory.class);
    EXCLUDE_PROXY_CACHE.add(JdkProxyFactory.class);
    EXCLUDE_PROXY_CACHE.add(CglibProxyFactory.class);
    EXCLUDE_PROXY_CACHE.add(ByteBuddyProxyFactory.class);
    EXCLUDE_PROXY_CACHE.add(JdkProxy.class);
    EXCLUDE_PROXY_CACHE.add(CglibProxy.class);
    EXCLUDE_PROXY_CACHE.add(ByteBuddyProxy.class);
  }

  private void addExcludeProxyClass(Class<?> clazz) {
    EXCLUDE_PROXY_CACHE.add(clazz);
  }

  @Override
  public Object postProcessBeforeInitialization(Object spi, SPIDefinition spiDefinition) throws CommonException {
    return doCreateSpiProxy(spi, spiDefinition);
  }

  @Override
  public Object postProcessAfterInitialization(Object spi, SPIDefinition spiDefinition) throws CommonException {
    return doCreateSpiProxy(spi, spiDefinition);
  }

  private Object doCreateSpiProxy(Object spi, SPIDefinition spiDefinition) throws CommonException {
    Class<?> clazz = spiDefinition.getSPIClass();
    try {
      if (EXCLUDE_PROXY_CACHE.contains(clazz)) {
        return spi;
      }
      if (PROXY_CACHE.containsKey(clazz)) {
        Proxy proxy = PROXY_CACHE.get(clazz);
        Object proxyObj = PROXY_INSTANCE_CACHE.get(clazz);
        if (ObjectUtils.equal(proxy.getSourceObject(), spi)) {
          return proxyObj;
        } else {
          return getProxy(spi, spiDefinition);
        }
      } else {
        return getProxy(spi, spiDefinition);
      }
    } catch (Throwable e) {
      throw new CommonException(e, "create proxy class: [{}] sourceObj: [{}] fail ", clazz, spi);
    }
  }

  private Object getProxy(Object spi, SPIDefinition spiDefinition) {
    Class<?> clazz = spiDefinition.getSPIClass();
    ConstructorMetaData constructorMetaData = MetaDataBuilder.getConstructorMetaData(spiDefinition.getConstructorMetaDataList());
    Proxy proxy = ProxyCreator.createProxy(clazz, spi, constructorMetaData.getParameterTypes(), constructorMetaData.getParameters());
    Object proxyObj = proxy.getProxy();
    PROXY_CACHE.put(clazz, proxy);
    PROXY_INSTANCE_CACHE.put(clazz, proxyObj);
    return proxyObj;
  }
}
